/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

package org.unidata.mdm.data.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.unidata.mdm.core.service.MetaModelService;
import org.unidata.mdm.core.type.data.RecordStatus;
import org.unidata.mdm.core.type.timeline.TimeInterval;
import org.unidata.mdm.core.type.timeline.Timeline;
import org.unidata.mdm.data.context.DeleteRelationRequestContext;
import org.unidata.mdm.data.context.DeleteRelationsRequestContext;
import org.unidata.mdm.data.context.GetRelationRequestContext;
import org.unidata.mdm.data.context.GetRelationsDigestRequestContext;
import org.unidata.mdm.data.context.GetRelationsRequestContext;
import org.unidata.mdm.data.context.GetRelationsTimelineRequestContext;
import org.unidata.mdm.data.context.MergeFromRelationRequestContext;
import org.unidata.mdm.data.context.MergeRelationsRequestContext;
import org.unidata.mdm.data.context.MergeToRelationRequestContext;
import org.unidata.mdm.data.context.RestoreFromRelationRequestContext;
import org.unidata.mdm.data.context.RestoreRelationsRequestContext;
import org.unidata.mdm.data.context.RestoreToRelationRequestContext;
import org.unidata.mdm.data.context.UpsertRelationRequestContext;
import org.unidata.mdm.data.context.UpsertRelationsRequestContext;
import org.unidata.mdm.data.dao.RelationsDAO;
import org.unidata.mdm.data.dto.DeleteRelationDTO;
import org.unidata.mdm.data.dto.DeleteRelationsDTO;
import org.unidata.mdm.data.dto.GetRelationDTO;
import org.unidata.mdm.data.dto.GetRelationsDTO;
import org.unidata.mdm.data.dto.MergeRelationDTO;
import org.unidata.mdm.data.dto.MergeRelationsDTO;
import org.unidata.mdm.data.dto.RelationDigestDTO;
import org.unidata.mdm.data.dto.RelationStateDTO;
import org.unidata.mdm.data.dto.RelationsBulkResultDTO;
import org.unidata.mdm.data.dto.RestoreRelationDTO;
import org.unidata.mdm.data.dto.RestoreRelationsDTO;
import org.unidata.mdm.data.dto.UpsertRelationDTO;
import org.unidata.mdm.data.dto.UpsertRelationsDTO;
import org.unidata.mdm.data.po.data.RelationEtalonPO;
import org.unidata.mdm.data.service.DataRelationsService;
import org.unidata.mdm.data.service.segments.relations.batch.RelationsDeleteConnectorExecutor;
import org.unidata.mdm.data.service.segments.relations.batch.RelationsMergeConnectorExecutor;
import org.unidata.mdm.data.service.segments.relations.batch.RelationsUpsertConnectorExecutor;
import org.unidata.mdm.data.service.segments.relations.delete.RelationDeleteConnectorExecutor;
import org.unidata.mdm.data.service.segments.relations.get.RelationGetConnectorExecutor;
import org.unidata.mdm.data.service.segments.relations.merge.RelationMergeConnectorExecutor;
import org.unidata.mdm.data.service.segments.relations.restore.RelationRestoreConnectorExecutor;
import org.unidata.mdm.data.service.segments.relations.upsert.RelationUpsertConnectorExecutor;
import org.unidata.mdm.data.type.apply.batch.impl.RelationDeleteBatchSetAccumulator;
import org.unidata.mdm.data.type.apply.batch.impl.RelationMergeBatchSetAccumulator;
import org.unidata.mdm.data.type.apply.batch.impl.RelationRestoreBatchSetAccumulator;
import org.unidata.mdm.data.type.apply.batch.impl.RelationUpsertBatchSetAccumulator;
import org.unidata.mdm.data.type.data.EtalonRelation;
import org.unidata.mdm.data.type.data.OriginRelation;
import org.unidata.mdm.data.type.keys.RecordEtalonKey;
import org.unidata.mdm.data.type.keys.RecordKeys;
import org.unidata.mdm.data.type.keys.RelationEtalonKey;
import org.unidata.mdm.data.type.keys.RelationKeys;
import org.unidata.mdm.meta.configuration.Descriptors;
import org.unidata.mdm.meta.type.RelativeDirection;
import org.unidata.mdm.system.service.ExecutionService;
import org.unidata.mdm.system.type.batch.BatchSetSize;
import org.unidata.mdm.system.type.runtime.MeasurementPoint;

/**
 * @author Mikhail Mikhailov Relations service component.
 */
@Component
public class DataRelationsServiceImpl implements DataRelationsService {
    /**
     * Get multiple connector/executor component.
     */
    @Autowired
    @Qualifier(RelationGetConnectorExecutor.SEGMENT_ID)
    private RelationGetConnectorExecutor relationGetConnectorExecutor;
    /**
     * Upsert multiple connector/executor component.
     */
    @Autowired
    @Qualifier(RelationUpsertConnectorExecutor.SEGMENT_ID)
    private RelationUpsertConnectorExecutor relationUpsertConnectorExecutor;
    /**
     * Delete multiple connector/executor component.
     */
    @Autowired
    @Qualifier(RelationDeleteConnectorExecutor.SEGMENT_ID)
    private RelationDeleteConnectorExecutor relationDeleteConnectorExecutor;
    /**
     * Merge delegate.
     */
    @Autowired
    @Qualifier(RelationMergeConnectorExecutor.SEGMENT_ID)
    private RelationMergeConnectorExecutor relationMergeConnectorExecutor;
    /**
     * Batch upsert relations.
     */
    @Autowired
    @Qualifier(RelationsUpsertConnectorExecutor.SEGMENT_ID)
    private RelationsUpsertConnectorExecutor relationsUpsertConnectorExecutor;
    /**
     * Batch delete relations.
     */
    @Autowired
    @Qualifier(RelationsDeleteConnectorExecutor.SEGMENT_ID)
    private RelationsDeleteConnectorExecutor relationsDeleteConnectorExecutor;
    /**
     * Batch merge relations.
     */
    @Autowired
    @Qualifier(RelationsMergeConnectorExecutor.SEGMENT_ID)
    private RelationsMergeConnectorExecutor relationsMergeConnectorExecutor;
    /**
     * Batch restore relations.
     */
    @Autowired
    @Qualifier(RelationRestoreConnectorExecutor.SEGMENT_ID)
    private RelationRestoreConnectorExecutor relationRestoreConnectorExecutor;
    /**
     * Relations vistory DAO.
     */
    @Autowired
    private RelationsDAO relationsVistoryDao;
    /**
     * Meta model service.
     */
    @Autowired
    private MetaModelService metaModelService;
    /**
     * Common relations functionality.
     */
    @Autowired
    private CommonRelationsComponent commonRelationsComponent;
    /**
     * The ES instance.
     */
    @Autowired
    private ExecutionService executionService;
    /**
     * Constructor.
     */
    public DataRelationsServiceImpl() {
        super();
    }
    /**
     * Loads relevant relations time line for the given relation identities and relation name.
     *
     * @param ctx the context
     * @return timeline
     */
    @Override
    public List<Timeline<OriginRelation>> loadTimelines(GetRelationsTimelineRequestContext ctx) {

        MeasurementPoint.start();
        try {

            Map<String, List<Timeline<OriginRelation>>> timelines = commonRelationsComponent.loadTimelines(ctx);
            return timelines.entrySet().stream()
                .map(Entry::getValue)
                .filter(CollectionUtils::isNotEmpty)
                .flatMap(Collection::stream)
                .filter(tl -> ! tl.isEmpty())
                .collect(Collectors.toList());

        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public List<Timeline<OriginRelation>> loadTimelines(GetRelationsRequestContext ctx) {
        return loadTimelines(GetRelationsTimelineRequestContext.builder(ctx).build());
    }
    /**
     * Gets a relation by simple request context.
     *
     * @param ctx the context
     * @return relation DTO
     */
    @Override
    public GetRelationDTO getRelation(GetRelationRequestContext ctx) {
        MeasurementPoint.start();
        try {
            return executionService.execute(ctx);
        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * Gets relations by simple request contexts.
     *
     * @param ctxts the contexts
     * @return relations DTO
     */
    @Override
    public List<GetRelationDTO> getRelations(List<GetRelationRequestContext> ctxts) {

        List<GetRelationDTO> result = new ArrayList<>(ctxts.size());
        for (GetRelationRequestContext ctx : ctxts) {
            GetRelationDTO val = getRelation(ctx);
            if (val != null) {
                result.add(val);
            }
        }
        return result;
    }
    /**
     * Gets the relations.
     *
     * @param ctx the context
     * @return relations DTO
     */
    @Override
    public GetRelationsDTO getRelations(GetRelationsRequestContext ctx) {
        return relationGetConnectorExecutor.execute(ctx, null);
    }
    /**
     * Actually upserts relation.
     *
     * @param ctx the context
     * @return result DTO
     */
    @Override
    @Transactional
    public UpsertRelationDTO upsertRelation(UpsertRelationRequestContext ctx) {
        MeasurementPoint.start();
        try {
            return executionService.execute(ctx);
        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * Upsert multiple updating relations call.
     *
     * @param ctxts the contexts to process
     * @return result (inserted/updated record)
     */
    @Override
    @Transactional
    public List<UpsertRelationDTO> upsertRelations(List<UpsertRelationRequestContext> ctxts) {
        List<UpsertRelationDTO> result = new ArrayList<>(ctxts.size());
        for (UpsertRelationRequestContext ctx : ctxts) {
            UpsertRelationDTO val = upsertRelation(ctx);
            if (val != null) {
                result.add(val);
            }
        }
        return result;
    }
    /**
     * Upsert relations call.
     *
     * @param ctx the context
     * @return result (inserted/updated records)
     */
    @Override
    @Transactional
    public UpsertRelationsDTO upsertRelations(UpsertRelationsRequestContext ctx) {
        MeasurementPoint.start();
        try {
            return relationUpsertConnectorExecutor.execute(ctx, null);
        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * Actually deletes a relation.
     *
     * @param ctx the context
     * @return result DTO
     */
    @Override
    @Transactional
    public DeleteRelationDTO deleteRelation(DeleteRelationRequestContext ctx) {
        MeasurementPoint.start();
        try {
            return executionService.execute(ctx);
        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * Deletes relations.
     *
     * @param ctxts the contexts
     * @return result DTO
     */
    @Override
    @Transactional
    public List<DeleteRelationDTO> deleteRelations(List<DeleteRelationRequestContext> ctxts) {
        List<DeleteRelationDTO> result = new ArrayList<>(ctxts.size());
        for (DeleteRelationRequestContext ctx : ctxts) {
            DeleteRelationDTO val = deleteRelation(ctx);
            if (val != null) {
                result.add(val);
            }
        }
        return result;
    }
    /**
     * Deletes relations.
     *
     * @param ctx the context
     * @return result DTO
     */
    @Override
    @Transactional
    public DeleteRelationsDTO deleteRelations(DeleteRelationsRequestContext ctx) {
        MeasurementPoint.start();
        try {
            return relationDeleteConnectorExecutor.execute(ctx, null);
        } finally {
            MeasurementPoint.stop();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public MergeRelationDTO mergeRelation(MergeFromRelationRequestContext ctx) {
        MeasurementPoint.start();
        try {
            return executionService.execute(ctx);
        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public MergeRelationDTO mergeRelation(MergeToRelationRequestContext ctx) {
        MeasurementPoint.start();
        try {
            return executionService.execute(ctx);
        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public MergeRelationsDTO mergeRelations(MergeRelationsRequestContext ctx) {
        MeasurementPoint.start();
        try {
            return relationMergeConnectorExecutor.execute(ctx, null);
        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RestoreRelationDTO restore(RestoreFromRelationRequestContext ctx) {
        MeasurementPoint.start();
        try {
            return executionService.execute(ctx);
        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RestoreRelationDTO restore(RestoreToRelationRequestContext ctx) {
        MeasurementPoint.start();
        try {
            return executionService.execute(ctx);
        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RestoreRelationsDTO restore(RestoreRelationsRequestContext ctx) {
        MeasurementPoint.start();
        try {
            return relationRestoreConnectorExecutor.execute(ctx, null);
        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * Loads all periods of all relations as list.
     *
     * @param keys        record from keys
     * @param operationId possibly existing operation id
     * @return collection of relations or empty list
     */
    // FIXME Kill this, refs from old backend only!
    // Used by COPY operation
    @Deprecated
    public List<EtalonRelation> loadActiveEtalonsRelationsByFromSideAsList(@Nonnull RecordKeys keys, String operationId) {
        return loadActiveEtalonsRelationsAsList(keys, true, operationId);
    }

    /**
     * Loads all periods of all relations as list by to side.
     *
     * @param keys        record from keys
     * @param operationId possibly existing operation id
     * @return collection of relations or empty list
     */
    // FIXME Kill this, refs from old backend only!
    // Used by COPY operation
    @Deprecated
    public List<EtalonRelation> loadActiveEtalonsRelationsByToSideAsList(@Nonnull RecordKeys keys, String operationId) {
        return loadActiveEtalonsRelationsAsList(keys, false, operationId);
    }

    /**
     * Loads all periods of all relations as list.
     *
     * @param keys        record from keys
     * @param byFromSide  by from side (true) or by to side (false)
     * @param operationId possibly existing operation id
     * @return collection of relations or empty list
     */
    // FIXME Kill this, refs from old backend only!
    // Used by COPY operation
    @Deprecated
    private List<EtalonRelation> loadActiveEtalonsRelationsAsList(@Nonnull RecordKeys keys, boolean byFromSide, String operationId) {

        MeasurementPoint.start();
        try {

            GetRelationsTimelineRequestContext ctx = GetRelationsTimelineRequestContext.builder()
                    .fetchByToSide(!byFromSide)
                    .forOperationId(operationId)
                    .fetchData(true)
                    .reduceReferences(true)
                    .build();

            ctx.keys(keys);

            Map<String, List<Timeline<OriginRelation>>> timelines = commonRelationsComponent.loadTimelines(ctx);
            return timelines.values().stream()
                    .flatMap(Collection::stream)
                    .flatMap(Timeline::stream)
                    .filter(TimeInterval::isActive)
                    .map(TimeInterval::<EtalonRelation>getCalculationResult)
                    .filter(Objects::nonNull)

                    .collect(Collectors.toList());

        } finally {
            MeasurementPoint.stop();
        }
    }

    /**
     * Loads etlon relations for etalon id, relation name and date.
     *
     * @param from        the object (left side, from) etalon id
     * @param name        relation name
     * @param asOf        the date
     * @param operationId the operation id
     * @return relations
     */
    public Map<RelationStateDTO, List<GetRelationDTO>> loadActiveEtalonsRelations(
            RecordKeys from,
            String name,
            Date asOf,
            Date lud,
            String operationId
    ) {
        return loadEtalonsRelations(
                from, name, asOf, lud, operationId, Collections.singletonList(RecordStatus.ACTIVE), Collections.singleton(RelativeDirection.FROM)
        );
    }

    public Map<RelationStateDTO, List<GetRelationDTO>> loadAllEtalonsRelations(
            RecordKeys from,
            String name,
            Date asOf,
            Date lud,
            String operationId
    ) {
        return loadEtalonsRelations(
                from,
                name,
                asOf,
                lud,
                operationId,
                Arrays.asList(RecordStatus.ACTIVE, RecordStatus.INACTIVE, RecordStatus.MERGED),
                EnumSet.allOf(RelativeDirection.class)
        );
    }

    public List<GetRelationDTO> loadRelationsToEtalon(GetRelationsRequestContext ctx) {
        return ctx.getRelationNames().stream()
                .flatMap(name ->
                        relationsVistoryDao.loadEtalonRelations(
                                UUID.fromString(ctx.getEtalonKey()),
                                name,
                                Collections.singletonList(RecordStatus.ACTIVE),
                                RelativeDirection.TO
                        ).stream()
                )
                .map(r -> new GetRelationDTO(
                        RelationKeys.builder()
                                .etalonKey(RelationEtalonKey.builder()
                                    .from(RecordEtalonKey.builder()
                                            .id(r.getFromEtalonId())
                                            .build())
                                    .to(RecordEtalonKey.builder()
                                            .id(r.getToEtalonId())
                                            .build())
                                    .id(r.getId())
                                    .status(r.getStatus())
                                    .build())
                                .relationName(r.getName())
                                .relationType(r.getRelationType())
                                .build()
                ))
                .collect(Collectors.toList());
    }

    private Map<RelationStateDTO, List<GetRelationDTO>> loadEtalonsRelations(
            RecordKeys recordKeys,
            String name,
            Date asOf,
            Date lud,
            String operationId,
            List<RecordStatus> statuses,
            Set<RelativeDirection> sides
    ) {

        MeasurementPoint.start();
        try {

            List<RelationEtalonPO> toEtalons = new ArrayList<>();
            if (sides.contains(RelativeDirection.FROM)) {
                toEtalons.addAll(
                        relationsVistoryDao.loadEtalonRelations(
                                UUID.fromString(recordKeys.getEtalonKey().getId()), name, statuses, RelativeDirection.FROM
                        )
                );
            }
            if (sides.contains(RelativeDirection.TO)) {
                toEtalons.addAll(
                        relationsVistoryDao.loadEtalonRelations(
                                UUID.fromString(recordKeys.getEtalonKey().getId()), name, statuses, RelativeDirection.TO
                        )
                );
            }

            if (toEtalons.isEmpty()) {
                return Collections.emptyMap();
            }

            List<GetRelationRequestContext> requestList = toEtalons.stream().map(
                    po -> GetRelationRequestContext.builder()
                            .relationEtalonKey(po.getId())
                            .forDate(asOf)
                            .forLastUpdate(lud)
                            .forOperationId(operationId)
                            .build())
                    .collect(Collectors.toList());

            GetRelationsRequestContext ctx = GetRelationsRequestContext.builder()
                    .relationsFrom(Collections.singletonMap(name, requestList))
                    .build();

            ctx.keys(recordKeys);

            GetRelationsDTO result = getRelations(ctx);
            return result.getRelations();

        } finally {
            MeasurementPoint.stop();
        }
    }

    /**
     * Gets relations digest.
     *
     * @param ctx the context
     * @return result
     */
    @Override
    public RelationDigestDTO loadRelatedEtalonIdsForDigest(GetRelationsDigestRequestContext ctx) {
        MeasurementPoint.start();
        try {

            if (!metaModelService.instance(Descriptors.DATA).isRelation(ctx.getRelName())) {
                return null;
            }

            Map<String, Integer> sourceSystemsMap = metaModelService.instance(Descriptors.SOURCE_SYSTEMS).getAscendingMap();
            return relationsVistoryDao.loadDigestDestinationEtalonIds(UUID.fromString(ctx.getEtalonId()), ctx.getRelName(),
                    ctx.getDirection(), sourceSystemsMap, null, ctx.getCount(), ctx.getPage() * ctx.getCount());

        } finally {
            MeasurementPoint.stop();
        }
    }

    /**
     * Check exist data relation by relation name
     *
     * @param relName the name
     * @return count
     */
    public boolean checkHasDataByRelationName(String relName) {
        return relationsVistoryDao.checkHasDataByRelationName(relName);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RelationsBulkResultDTO batchUpsertRelations(List<UpsertRelationsRequestContext> ctxs) {
        return batchUpsertRelations(ctxs, true);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RelationsBulkResultDTO batchUpsertRelations(List<UpsertRelationsRequestContext> ctxs, boolean abortOnFailure) {
        RelationUpsertBatchSetAccumulator rubsa = new RelationUpsertBatchSetAccumulator(ctxs.size(), false, false);
        rubsa.setBatchSetSize(BatchSetSize.SMALL);
        rubsa.setAbortOnFailure(abortOnFailure);
        rubsa.statistics().collectResults(true);
        rubsa.charge(ctxs);
        return batchUpsertRelations(rubsa);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RelationsBulkResultDTO batchUpsertRelations(RelationUpsertBatchSetAccumulator accumulator) {
        return relationsUpsertConnectorExecutor.execute(accumulator, null);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RelationsBulkResultDTO batchDeleteRelations(List<DeleteRelationsRequestContext> ctxs) {
        return batchDeleteRelations(ctxs, true);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RelationsBulkResultDTO batchDeleteRelations(List<DeleteRelationsRequestContext> ctxs, boolean abortOnFailure) {
        RelationDeleteBatchSetAccumulator rdbsa = new RelationDeleteBatchSetAccumulator(500, false);
        rdbsa.setBatchSetSize(BatchSetSize.SMALL);
        rdbsa.setAbortOnFailure(abortOnFailure);
        rdbsa.statistics().collectResults(true);
        rdbsa.charge(ctxs);
        return batchDeleteRelations(rdbsa);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RelationsBulkResultDTO batchDeleteRelations(RelationDeleteBatchSetAccumulator accumulator) {
        return relationsDeleteConnectorExecutor.execute(accumulator, null);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RelationsBulkResultDTO batchMergeRelations(List<MergeRelationsRequestContext> ctxs) {
        return batchMergeRelations(ctxs, true);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RelationsBulkResultDTO batchMergeRelations(List<MergeRelationsRequestContext> ctxs, boolean abortOnFailure) {
        RelationMergeBatchSetAccumulator rdbsa = new RelationMergeBatchSetAccumulator(ctxs.size());
        rdbsa.setBatchSetSize(BatchSetSize.SMALL);
        rdbsa.setAbortOnFailure(abortOnFailure);
        rdbsa.statistics().collectResults(true);
        rdbsa.charge(ctxs);
        return batchMergeRelations(rdbsa);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RelationsBulkResultDTO batchMergeRelations(RelationMergeBatchSetAccumulator accumulator) {
        return relationsMergeConnectorExecutor.execute(accumulator, null);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RelationsBulkResultDTO batchRestoreRelations(List<RestoreRelationsRequestContext> ctxs) {
        return batchRestoreRelations(ctxs, false);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RelationsBulkResultDTO batchRestoreRelations(List<RestoreRelationsRequestContext> ctxs, boolean abortOnFailure) {
        RelationRestoreBatchSetAccumulator rrbsa = new RelationRestoreBatchSetAccumulator(ctxs.size(), false, false);
        rrbsa.setBatchSetSize(BatchSetSize.SMALL);
        rrbsa.setAbortOnFailure(abortOnFailure);
        rrbsa.statistics().collectResults(true);
        rrbsa.charge(ctxs);
        return batchRestoreRelations(rrbsa);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public RelationsBulkResultDTO batchRestoreRelations(RelationRestoreBatchSetAccumulator accumulator) {
        // TODO: Generated
        return null;
    }
}
